package org.budo.dubbo.protocol.http.invocation.adapter.impl;

import java.io.IOException;
import java.lang.reflect.Method;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.dubbo.protocol.http.exception.remoting.RemotingRuntimeException;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.protocol.http.invocation.adapter.AbstractInvocationAdapter;
import org.budo.dubbo.protocol.http.invocation.adapter.ProtocolRequest;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.dubbo.protocol.http.util.BudoSerializationUtil;
import org.budo.dubbo.protocol.http.util.SomeUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.dubbo.common.serialize.Serialization;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class DubboRpcInvocationAdapter extends AbstractInvocationAdapter {
    @Override
    public BudoDubboRpcInvocation adaptInvocation(ProtocolRequest protocolRequest) throws Throwable {
        String requestBody = this.getProtocolRequestBody(protocolRequest);

        return this.requestBodyToInvocation(protocolRequest, requestBody);
    }

    private BudoDubboRpcInvocation requestBodyToInvocation(ProtocolRequest protocolRequest, String requestBody) throws IOException {
        byte[] requestBytes = requestBody.getBytes(Consts.UTF_8);

        Serialization serialization = BudoSerializationUtil.getSerialization(protocolRequest);
        BudoDubboRpcInvocation budoDubboRpcInvocation = BudoSerializationUtil.deserialize(serialization, requestBytes, BudoDubboRpcInvocation.class);

        String interfaceName = protocolRequest.getHeader(Consts.HeaderKey.InterfaceName);

        budoDubboRpcInvocation.setInterfaceName(interfaceName);
        budoDubboRpcInvocation.setRequestBody(requestBody);

        Method matchedMethod = BudoReflectionUtil.getMatchedMethodCached(interfaceName, budoDubboRpcInvocation.getMethodName());

        if (null == matchedMethod) {
            matchedMethod = SomeUtil.findMatchedMethod(interfaceName, budoDubboRpcInvocation.getMethodName());
        }

        if (null == matchedMethod) {
            String message = "#51 method not matched, interfaceName=" + interfaceName //
                    + ", budoDubboRpcInvocation=" + budoDubboRpcInvocation //
                    + ", protocolRequest=" + protocolRequest;
            log.error(message);
            throw new RemotingRuntimeException(message);
        }

        // @ResponseBody
        ResponseBody responseBodyAnnotation = matchedMethod.getAnnotation(ResponseBody.class);
        budoDubboRpcInvocation.setResponseBody(null != responseBodyAnnotation);

        Object[] callingArguments = budoDubboRpcInvocation.getArguments();

        Class<?> matchedInterface = ReflectUtil.classForNameOrNull(interfaceName);
        Object[] parsedArguments = this.parseArguments(budoDubboRpcInvocation, matchedInterface, matchedMethod, callingArguments);

        budoDubboRpcInvocation.setArguments(parsedArguments);

        this.readAttachments(protocolRequest, budoDubboRpcInvocation);

        log.debug("#66 budoDubboRpcInvocation={}, callingArguments={}", budoDubboRpcInvocation, callingArguments);

        return budoDubboRpcInvocation;
    }

    /**
     * 参数转换
     */
    private Object[] parseArguments(BudoDubboRpcInvocation budoDubboRpcInvocation, Class<?> matchedInterface, Method matchedMethod, Object[] callingArguments) {
        Class<?>[] parameterTypes = budoDubboRpcInvocation.getParameterTypes();
        Object[] parsedArguments = new Object[callingArguments.length];

        // 对象数据类型转换
        for (int index = 0; index < parameterTypes.length; index++) {
            Class<?> parameterType = parameterTypes[index];
            Object argumentValue = callingArguments[index];
            parsedArguments[index] = this.parseArgumentValue(matchedInterface, matchedMethod, index, parameterType, argumentValue);
        }

        return parsedArguments;
    }
}
